package com.liruo.treasureattic.starter.oss.client;

import com.liruo.treasureattic.starter.oss.model.future.UploadFuture;
import com.liruo.treasureattic.starter.oss.model.properties.TencentCloudOssProperties;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.model.GetObjectRequest;
import com.qcloud.cos.model.PutObjectRequest;
import com.qcloud.cos.region.Region;
import com.qcloud.cos.transfer.TransferManager;
import com.qcloud.cos.transfer.TransferManagerConfiguration;
import com.qcloud.cos.transfer.Upload;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Author:liruo
 * @Date:2022-12-05-17:14:28
 * @Desc
 *  方法的参数key是指在存储桶的路径，必须是合法的文件格式，必须不以/开头
 *  同key会覆盖
 */

public class TencentCloudOssClient implements OssClient{
    private  TransferManager transferManager;
    public final TencentCloudOssProperties properties;
    private final boolean useDefaultLocalRootDir;
    public TencentCloudOssClient(TencentCloudOssProperties tencentCloudOssProperties) {
        this.properties = tencentCloudOssProperties;
        useDefaultLocalRootDir = properties.getDefaultDownloadLocalDir() != null &&
                !properties.getDefaultDownloadLocalDir().isBlank();
    }
    @PostConstruct
    private void init(){
        COSCredentials cred = new BasicCOSCredentials(properties.getSecretId(), properties.getSecretKey());
        Region region = new Region(properties.getRegion());
        ClientConfig clientConfig = new ClientConfig(region);
        COSClient client = new COSClient(cred, clientConfig);
        // 高级接口传输类
        // 线程池大小，建议在客户端与 COS 网络充足（例如使用腾讯云的 CVM，同地域上传 COS）的情况下，设置成16或32即可，可较充分的利用网络资源
        // 对于使用公网传输且网络带宽质量不高的情况，建议减小该值，避免因网速过慢，造成请求超时。
        ExecutorService threadPool = Executors.newFixedThreadPool(properties.getThreads());
        // 传入一个 threadpool, 若不传入线程池，默认 TransferManager 中会生成一个单线程的线程池。
        transferManager = new TransferManager(client, threadPool);
        // 设置高级接口的分块上传阈值和分块大小为10MB
        TransferManagerConfiguration transferManagerConfiguration = new TransferManagerConfiguration();
        transferManagerConfiguration.setMultipartUploadThreshold(10 * 1024 * 1024);
        transferManagerConfiguration.setMinimumUploadPartSize(10 * 1024 * 1024);
        transferManager.setConfiguration(transferManagerConfiguration);
    }
    @PreDestroy
    private void destroy(){
        transferManager.shutdownNow();
    }

    /**
     * @param key  必须不以/开头
     */
    private String checkPathKey(String key){
        if(key.startsWith("/") || key.startsWith("\\")){
            key = key.substring(1);
        }
        if(key.contains("\\")){
            key = key.replace("\\", "/");
        }
        return key;
    }
    private String handlerLocalPath(String destFileRelativePath){
        String defaultDownloadLocalDir = properties.getDefaultDownloadLocalDir();
        if(useDefaultLocalRootDir){
            return properties.getDefaultDownloadLocalDir() + defaultDownloadLocalDir;
        }else{
            return destFileRelativePath;
        }
    }

    /**
     *
     * @param key   对象在存储桶里的文件路径
     * @return
     */
    @Override
    public void uploadAsync(byte[] bytes, String key){
        uploadAsyncBase(bytes, key);
    }
    @Override
    public void uploadAsync(File file, String key){
        uploadAsyncBase(file, key);
    }
    @Override
    public UploadFuture uploadAsyncR(byte[] bytes, String key){
        Upload upload = uploadAsyncBase(bytes, key);
        return new UploadFuture(upload);
    }
    @Override
    public UploadFuture uploadAsyncR(File file, String key){
        Upload upload = uploadAsyncBase(file, key);
        return new UploadFuture(upload);
    }
    private Upload uploadAsyncBase(byte[] bytes, String key) {
        key = checkPathKey(key);
        ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
        PutObjectRequest putReq =  new PutObjectRequest(
                properties.getDefaultBucket(),
                key,
                bis,
                null//null会在putObject时创建一个--和传File对象一样，腾讯云会自动检查数据类型,其他不知道，但是像图片这样的有后缀的文件可以识别
        );
        //同步上传
//        Upload uploadd = transferManager.upload(putReq);
//        uploadd.waitForUploadResult();
        return transferManager.upload(putReq);
    }
    private Upload uploadAsyncBase(File file, String key) {
        key = checkPathKey(key);
        PutObjectRequest putReq;
        putReq =  new PutObjectRequest(
                properties.getDefaultBucket(),
                key,
                file
        );
        //同步上传
//        Upload uploadd = transferManager.upload(putReq);
//        uploadd.waitForUploadResult();
        return transferManager.upload(putReq);
    }
    @Override
    public void multiUploadAsync(Map<String, Object> map){
        multiUpload(map, false);
    }
    /**
     * 等待最后一个上传完
     * @return objects的urls
     */
    @Override
    public void multiUploadWaitLast(Map<String, Object> map){
        multiUpload(map, true);
    }

    private void multiUpload(Map<String, Object> map, boolean wait) {
        if(CollectionUtils.isEmpty(map)){
            throw new RuntimeException("must has objs");
        }
        Optional<Object> first = map.values().stream().findFirst();
        boolean isFile = false;
        if(first.isPresent()){
            Object object = first.get();
            if(object instanceof File){
                isFile = true;
            }
        }else{
            throw new RuntimeException("null Objects");
        }

        Iterator<Map.Entry<String, Object>> entryI = map.entrySet().iterator();
        UploadFuture uploadFuture = null;
        while(entryI.hasNext()){
            Map.Entry<String, Object> next = entryI.next();
            Object value = next.getValue();
            String key = next.getKey();
            if(isFile){
                uploadFuture = uploadAsyncR((File) value, key);
            }else{
                uploadFuture = uploadAsyncR((byte[]) value, key);
            }
        }
        try {
            if(wait && uploadFuture != null){
                uploadFuture.get();
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void downloadToLocal(String key, String destFilePath){
        key = checkPathKey(key);
        GetObjectRequest getReq = new GetObjectRequest(
                properties.getDefaultBucket(),
                 key
        );
        String filepath = handlerLocalPath(destFilePath);
        transferManager.download(getReq, new File(filepath));
    }

    @Override
    public String getUrl(String key){
        key = checkPathKey(key);
        GetObjectRequest getUrlReq = new GetObjectRequest(
                properties.getDefaultBucket(),
                 key
        );
        //generatePresignedUrl生成带sign的url
        //getObjectUrl不带sign
        URL objectUrl = ((COSClient)transferManager.getCOSClient()).getObjectUrl(getUrlReq);
        return objectUrl.toString();
    }

    @Override
    public List<String> multiGetUrl(List<String> keys){
        COSClient cosClient = (COSClient)transferManager.getCOSClient();
        ArrayList<String> urls = new ArrayList<>(keys.size());
        keys.forEach(key->urls.add(getUrl(key)));
        return urls;
    }

}
